package com.roundarchisobar.njariwala.netflix.service;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.client.RestTemplate;


import com.roundarchisobar.njariwala.netflix.constants.NetflixConstants;
import com.roundarchisobar.njariwala.netflix.dto.NetflixBaseEntity;
import com.roundarchisobar.njariwala.netflix.dto.NetflixRequestParam;

public abstract class AbstractNetflixService implements NetflixConstants {

	private static final Logger logger = LoggerFactory.getLogger(AbstractNetflixService.class);

	private static final String NETFLIX_API_SERVER_URL = "http://api-public.netflix.com/";
	private static final String DEFAULT_VERSION = "1.0";
	private static final String DEFAULT_SIGNATURE_METHOD = "HMAC-SHA1";
	private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";

	private static Base64 base64 = new Base64();

	@Autowired
	protected RestTemplate restTemplate;

	@Autowired
	protected NetflixUtilityService netflixUtilityService;
	
	@Autowired
	private CacheManager cacheManager;


	@Value(value = "#{appProperties['netflix.api.consumer.key']}")
	protected String consumerKey;

	@Value(value = "#{appProperties['netflix.api.shared.secret']}")
	protected String sharedSecret;
	

	protected Object xmlToNetflixObject(String data, Class<? extends NetflixBaseEntity> clazz) {
		Object obj = null;
		try {
			JAXBContext jc = JAXBContext.newInstance(clazz);
			Unmarshaller u = jc.createUnmarshaller();
			obj = u.unmarshal(new ByteArrayInputStream(data.getBytes()));
		} catch (Exception ex) {
			logger.info("xmlToObject method failed" + ex);
		}
		return obj;
	}

	protected Object makeCall(List<NetflixRequestParam> netflixRequestParams, Class<? extends NetflixBaseEntity> clazz,
			String requestType) {
		String response = makeCallForStringResponse(netflixRequestParams,requestType);
		logger.info("Response:" + response);
		return xmlToNetflixObject(response, clazz);
	}
	
	protected String makeCallForStringResponse(List<NetflixRequestParam> netflixRequestParams,String requestType){
		String URL = generateURLForRestCall(netflixRequestParams, requestType);
		Cache cache=cacheManager.getCache("netflixData");
		String response = null;
		if(cache != null && requestType.startsWith("http://")){
			Element e = cache.get(requestType);
			if(e != null){
				response = (String)e.getObjectValue();
			}
		}
		if(response == null){
			logger.info("not found in cache:"+requestType);
			response = restTemplate.getForObject(URL, String.class);
			if(requestType.startsWith("http://")){
				cache.put(new Element(requestType, response));
			}
			
		}else{
			logger.info("found in cache:"+requestType);
		}
		return response;
	}

	protected String generateURLForRestCall(List<NetflixRequestParam> netflixRequestParams, String requestType) {
		StringBuilder sb = new StringBuilder();
		if (!requestType.startsWith("http://")) {
			sb.append(NETFLIX_API_SERVER_URL);
		}
		sb.append(requestType);
		sb.append("?");
		sb.append(generateNetflixParamURL(netflixRequestParams));
		return sb.toString();
	}

	protected String createSignature(List<NetflixRequestParam> netflixRequestParams, String requestType) {
		String signature = null;
		try {
			StringBuilder sb = new StringBuilder();
			sb.append("GET&");
			if (!requestType.startsWith("http://")) {
				sb.append(netflixUtilityService.getEncodedString(NETFLIX_API_SERVER_URL));
			}
			sb.append(netflixUtilityService.getEncodedString(requestType));
			sb.append("&");

			String data = sb.append(
					netflixUtilityService.getEncodedString(generateNetflixParamURL(netflixRequestParams))).toString();
			//logger.info("signature function input:" + data);

			// get an hmac_sha1 key from the raw key bytes
			SecretKeySpec signingKey = new SecretKeySpec((sharedSecret + "&").getBytes(UTF_8), HMAC_SHA1_ALGORITHM);
			// get an hmac_sha1 Mac instance and initialize with the signing key
			Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
			mac.init(signingKey);

			signature = new String(base64.encode(mac.doFinal(data.toString().getBytes(UTF_8))), UTF_8).trim();
			//logger.info(signature);
		} catch (Exception ex) {
			logger.error("createSignature method failed");
		}
		return signature;

	}

	protected List<NetflixRequestParam> getInitialRequestList() {
		List<NetflixRequestParam> netflixRequestParams = new ArrayList<NetflixRequestParam>();
		netflixRequestParams.add(new NetflixRequestParam(OAUTH_CONSUMER_KEY, consumerKey));
		netflixRequestParams.add(new NetflixRequestParam(OAUTH_NONCE, netflixUtilityService.getNonce()));
		netflixRequestParams.add(new NetflixRequestParam(OAUTH_TIMESTAMP, netflixUtilityService.getTimestamp()));
		netflixRequestParams.add(new NetflixRequestParam(OAUTH_VERSION, DEFAULT_VERSION));
		netflixRequestParams.add(new NetflixRequestParam(OAUTH_SIGNATURE_METHOD, DEFAULT_SIGNATURE_METHOD));
		return netflixRequestParams;
	}

	protected String generateNetflixParamURL(List<NetflixRequestParam> netflixRequestParams) {
		Collections.sort(netflixRequestParams);
		StringBuilder requestParamsStr = new StringBuilder();
		int i = 0;
		for (NetflixRequestParam netflixRequestParam : netflixRequestParams) {
			if (++i != 1)
				requestParamsStr.append("&");
			requestParamsStr.append(netflixRequestParam.getName());
			requestParamsStr.append("=");
			String value = netflixRequestParam.getValue();
			if (netflixRequestParam.getName().equals(TERM)) {
				// value = netflixUtilityService.getEncodedString(value);
			}
			requestParamsStr.append(value);
		}
		return requestParamsStr.toString();
	}
}
